/*
  +----------------------------------------------------------------------+
  | PHP Version 7                                                        |
  +----------------------------------------------------------------------+
  | Copyright (c) 1997-2018 The PHP Group                                |
  +----------------------------------------------------------------------+
  | This source file is subject to version 3.01 of the PHP license,      |
  | that is bundled with this package in the file LICENSE, and is        |
  | available through the world-wide-web at the following url:           |
  | http://www.php.net/license/3_01.txt                                  |
  | If you did not receive a copy of the PHP license and are unable to   |
  | obtain it through the world-wide-web, please send a note to          |
  | license@php.net so we can mail you a copy immediately.               |
  +----------------------------------------------------------------------+
  | Author:                                                              |
  +----------------------------------------------------------------------+
*/

/* $Id$ */
extern "C"{
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
#include "php.h"
#include "php_ini.h"
#include "ext/standard/info.h"
}
#include <iostream>
#include <sstream>
#include <map>
#include <unistd.h>
#include <math.h>

#include "opencv2/opencv.hpp"
#include "opencv2/xfeatures2d.hpp"

#include "php_tclip.h"

using namespace cv;
using namespace std;


/* If you declare any globals in php_tclip.h uncomment this:
ZEND_DECLARE_MODULE_GLOBALS(tclip)
*/
ZEND_DECLARE_MODULE_GLOBALS(tclip)
/* True global resources - no need for thread safety here */
static int le_tclip;
static CascadeClassifier face_cascade;

#if PHP_MAJOR_VERSION < 7
#define tcp_size_t int
#else
#define tcp_size_t size_t
#endif


struct WATERMARK{
    string text;
    int text_len;
    string color = "#ffffff";
    int red = 255;
    int green = 255;
    int blue = 255;
    int font = 0;
    int x = 0;
    int y = 0;
    int thickness = 1;
    double scale = 0.8f;
};


/* {{{ PHP_INI
 */
/* Remove comments and fill if you need to have entries in php.ini
PHP_INI_BEGIN()
    STD_PHP_INI_ENTRY("tclip.global_value",      "42", PHP_INI_ALL, OnUpdateLong, global_value, zend_tclip_globals, tclip_globals)
    STD_PHP_INI_ENTRY("tclip.global_string", "foobar", PHP_INI_ALL, OnUpdateString, global_string, zend_tclip_globals, tclip_globals)
PHP_INI_END()
*/
/* }}} */

PHP_INI_BEGIN()
   STD_PHP_INI_ENTRY("tclip.face_config_path", "/usr/local/share/OpenCV/haarcascades/haarcascade_frontalface_alt.xml", PHP_INI_ALL, OnUpdateString, face_config_path, zend_tclip_globals, tclip_globals)
PHP_INI_END()

/* Remove the following function when you have successfully modified config.m4
   so that your module can be compiled into PHP, it exists only for testing
   purposes. */


/* The previous line is meant for vim and emacs, so it can correctly fold and
   unfold functions in source code. See the corresponding marks just before
   function definition, where the functions purpose is also documented. Please
   follow this convention for the convenience of others editing your code.
*/


/* {{{ php_tclip_init_globals
 */
/* Uncomment this function if you have INI entries
static void php_tclip_init_globals(zend_tclip_globals *tclip_globals)
{
	tclip_globals->global_value = 0;
	tclip_globals->global_string = NULL;
}
*/
/* }}} */
static void php_tclip_init_globals(zend_tclip_globals *tclip_globals)
{
	tclip_globals->face_cascade = &face_cascade;
	tclip_globals->face_config_path = NULL;
}


int detectFace(Mat &img TSRMLS_DC)
{
	//rect对象用来存储一个矩形框的左上角坐标、宽度和高度。
    std::vector<Rect> faces;

    Mat imgGray;
    int faceSize;
    int Y;
    
    //二值图像
    cvtColor(img, imgGray, COLOR_BGR2GRAY);
    //直方图均衡化：是一种提高图像对比度的方法，拉伸图像灰度值范围。
    equalizeHist(imgGray, imgGray);
    //把检测到的人脸等用矩形（或者圆形等其他图形）画出来
    ((CascadeClassifier *)TCLIP_G(face_cascade))->detectMultiScale( imgGray, faces, 1.1, 2, 0|CASCADE_SCALE_IMAGE, Size(30, 30) );
	
    faceSize = faces.size();

    if( faceSize>0){
        Y = faces[faceSize-1].y - faces[faceSize-1].height/2;
        if( Y>img.size().height/2){
            return -1;
        }
        return Y<0?0:Y;
     }
    return -1;
}


int detectCharacter(Mat &img TSRMLS_DC)
{
	//特征点X坐标开始位置 
    int startX = 0;
    //特征点X坐标结束位置
    int endX = 0;
    //Y坐标段数字索引
    int sectionIndex = 0;
    //每个Y坐标段中特征点的数量
    map<int, int> sectionNum;
    //总共特征点数量
    int total = 0;
    //每个Y坐标段的平均特征点数量
    int avg = 0;
    //需要连续的阀值
    int conNum = 4;
    //
    int flag = 0;
    //
    int counter = 0;
    //
    int Y = 0;
    //指针
    vector<KeyPoint> keypoints;
    //定义一个SurfFeatureDetector（SURF） 特征检测类对象
    Ptr<xfeatures2d::SURF> detector = cv::xfeatures2d::SURF::create();

    if( detector.empty()){
        php_error_docref(NULL TSRMLS_CC, E_WARNING, "Can not create detector or descriptor exstractor or descriptor matcher of given types!");
        return -1;
    }

    startX = 0;
    endX = img.size().width;

    detector->detect(img, keypoints);
    for( vector<KeyPoint>::iterator i = keypoints.begin(); i != keypoints.end(); i++){
        if( i->pt.x>startX  && i->pt.x<endX ) {
            sectionIndex = (int)ceil(i->pt.y/10);
            sectionNum[sectionIndex] = sectionNum[sectionIndex]+1;
            total = total+1;
        }
    }

    if( sectionNum.size()==0){
        return -1;
    }

    avg = total/sectionNum.size();
    
    //检测特征点分布是否均匀
    int sliceTotal = 10;
    int sliceNum = sectionNum.size() / sliceTotal;
    int sliceCounter = 0;

    for( int m=0; m<sliceTotal; m++){
        for ( int n = m* sliceNum; n<(m+1)*sliceNum; n++){
            if( sectionNum[n]>=avg){
                sliceCounter++;
                break;
            }
        }
    }

    if(sliceCounter >= sliceTotal){
        return -1;
    }
    //检测特征点主要分布区域[找最开始连续大于avg的Y]
    for( map<int, int>::iterator i = sectionNum.begin(); i != sectionNum.end(); i++){
        if(i->second >= avg && flag == 0){
            counter++;
        }else{
            counter = 0;
        }

        if(counter >= conNum && flag==0){
            Y = i->first;
            flag = 1;
        }
    }

    if( Y > conNum && Y<img.size().height/4){
        return (Y - conNum - 11) * sliceTotal < 0 ? 0 : (Y-conNum-11)*sliceTotal;
    }else if( Y>conNum ){
        return ( Y-conNum )* sliceTotal;
    }
    return Y*sliceTotal;
}


static inline void convert_color_hex2rgb(const char *colorStr, int* r, int* g, int* b)
{
	std::string colorRaw(colorStr);

	if (colorRaw.at(0) == '#') {
		colorRaw = colorRaw.erase(0, 1);
	}

	std::istringstream(colorRaw.substr(0,2)) >> std::hex >> *r;
	std::istringstream(colorRaw.substr(2,2)) >> std::hex >> *g;
	std::istringstream(colorRaw.substr(4,2)) >> std::hex >> *b;
}


static inline int tclip_zend_hash_find(HashTable *ht, char *k, int len, void **v) {
#if PHP_MAJOR_VERSION < 7
    zval **tmp = NULL;
    if (zend_hash_find(ht, k, len, (void **) &tmp) == SUCCESS) {
        *v = *tmp;
        return SUCCESS;
    } else {
        *v = NULL;
        return FAILURE;
    }
#else
    zval *value = zend_hash_str_find(ht, k, len - 1);
    if (value == NULL) {
    	*v = NULL;
        return FAILURE;
    } else {
    	*v = (void *)value;
        return SUCCESS;
    }
#endif
}

/* {{{ PHP_MINIT_FUNCTION
 */
PHP_MINIT_FUNCTION(tclip)
{
	/* If you have INI entries, uncomment these lines
	REGISTER_INI_ENTRIES();
	*/
	REGISTER_INI_ENTRIES();
	string face_config_path = 0 == strlen(TCLIP_G(face_config_path)) ? "/usr/local/share/opencv4/haarcascades/haarcascade_frontalface_default.xml" : TCLIP_G(face_config_path);
	if (!face_cascade.load( face_config_path ) ){
		php_error_docref(NULL TSRMLS_CC, E_WARNING, "can not load classifier file！%s", face_config_path.c_str());
		return FAILURE;
	}
	TCLIP_G(face_cascade) = &face_cascade;

	return SUCCESS;
}
/* }}} */

/* {{{ PHP_MSHUTDOWN_FUNCTION
 */
PHP_MSHUTDOWN_FUNCTION(tclip)
{
	/* uncomment this line if you have INI entries
	UNREGISTER_INI_ENTRIES();
	*/
	UNREGISTER_INI_ENTRIES();
	return SUCCESS;
}
/* }}} */

/* Remove if there's nothing to do at request start */
/* {{{ PHP_RINIT_FUNCTION
 */
PHP_RINIT_FUNCTION(tclip)
{
#if defined(COMPILE_DL_TCLIP) && defined(ZTS)
	ZEND_TSRMLS_CACHE_UPDATE();
#endif
	return SUCCESS;
}
/* }}} */

/* Remove if there's nothing to do at request end */
/* {{{ PHP_RSHUTDOWN_FUNCTION
 */
PHP_RSHUTDOWN_FUNCTION(tclip)
{
	return SUCCESS;
}
/* }}} */

/* {{{ PHP_MINFO_FUNCTION
 */
PHP_MINFO_FUNCTION(tclip)
{
	php_info_print_table_start();
	php_info_print_table_header(2, "tclip support", "enabled");
	php_info_print_table_header(2, "version", PHP_TCLIP_VERSION);
	php_info_print_table_end();

	/* Remove comments if you have entries in php.ini
	DISPLAY_INI_ENTRIES();
	*/
	DISPLAY_INI_ENTRIES();
}
/* }}} */

/* {{{ tclip_functions[]
 *
 * Every user visible function must have an entry in tclip_functions[].
 */
const zend_function_entry tclip_functions[] = {
	PHP_FE(tclip,	NULL)		/* For testing, remove later. */
	{NULL, NULL, NULL}	/* Must be the last line in tclip_functions[] */
};
/* }}} */

/* {{{ tclip_module_entry
 */
zend_module_entry tclip_module_entry = {
	STANDARD_MODULE_HEADER,
	PHP_TCLIP_NAME,
	tclip_functions,
	PHP_MINIT(tclip),
	PHP_MSHUTDOWN(tclip),
	PHP_RINIT(tclip),		/* Replace with NULL if there's nothing to do at request start */
	PHP_RSHUTDOWN(tclip),	/* Replace with NULL if there's nothing to do at request end */
	PHP_MINFO(tclip),
	PHP_TCLIP_VERSION,
	STANDARD_MODULE_PROPERTIES
};
/* }}} */



/* Every user-visible function in PHP should document itself in the source */
/* {{{ proto string confirm_tclip_compiled(string arg)
   Return a string to confirm that the module is compiled in */
PHP_FUNCTION(tclip)
{
	
	char *sourcePath;
    char *destPath;
	tcp_size_t sourceLen, destLen;
    long destWidth, destHeight;
	zval *z_watermark_cfg = NULL;
	WATERMARK watermark;
	zval *z_tmp_v;
	int used_color = 0;

	float ratioWidth = 0,  
          ratioHeight = 0, 
          ratio = 0;
    int clipTop = 0,
        clipBottom = 0,
	    clipLeft = 0,
	    clipRight = 0;
    int result = 0;
    Mat image, destImage;

    HashTable *h_watermark_cfg;
    Size tmpSize;

    /* Get function parameters and do error-checking. */
    if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, 
	"ssll|z", 
	&sourcePath,&sourceLen,
	&destPath,&destLen,
	&destWidth,
	&destHeight,
	&z_watermark_cfg) == FAILURE) 
	{
		php_error_docref(NULL TSRMLS_CC, E_WARNING, "params is err :(");
		return;
	}
	
	if (z_watermark_cfg != NULL) {
		if (Z_TYPE_P(z_watermark_cfg) != IS_ARRAY && Z_TYPE_P(z_watermark_cfg) != IS_STRING) {
			php_error_docref(NULL TSRMLS_CC, E_WARNING, "the fifth param type must be array or string");
			RETURN_FALSE;
		}

		switch (Z_TYPE_P(z_watermark_cfg)) {
			case IS_ARRAY:
				h_watermark_cfg = Z_ARRVAL_P(z_watermark_cfg);
				//text
				if (tclip_zend_hash_find(h_watermark_cfg, ZEND_STRS("text"), (void **) &z_tmp_v) == SUCCESS) {
					convert_to_string(z_tmp_v);
					watermark.text = Z_STRVAL_P(z_tmp_v);
					watermark.text_len = Z_STRLEN_P(z_tmp_v);
				}
				if (tclip_zend_hash_find(h_watermark_cfg, ZEND_STRS("font"), (void **) &z_tmp_v) == SUCCESS) {
					convert_to_long(z_tmp_v);
					watermark.font = (int) Z_LVAL_P(z_tmp_v);
					if (watermark.font < FONT_HERSHEY_SIMPLEX || watermark.font >FONT_HERSHEY_SCRIPT_COMPLEX) {
						watermark.font = FONT_HERSHEY_SIMPLEX;
					}
				}
				//point x y
				if (tclip_zend_hash_find(h_watermark_cfg, ZEND_STRS("x"), (void **) &z_tmp_v) == SUCCESS) {
					convert_to_long(z_tmp_v);
					watermark.x = (int) Z_LVAL_P(z_tmp_v);
				}
				if (tclip_zend_hash_find(h_watermark_cfg, ZEND_STRS("y"), (void **) &z_tmp_v) == SUCCESS) {
					convert_to_long(z_tmp_v);
					watermark.y = (int) Z_LVAL_P(z_tmp_v);
				}
				//font scale
				if (tclip_zend_hash_find(h_watermark_cfg, ZEND_STRS("scale"), (void **) &z_tmp_v) == SUCCESS) {
					convert_to_double(z_tmp_v);
					watermark.scale = (double)Z_DVAL_P(z_tmp_v);
				}
				//color
				if (tclip_zend_hash_find(h_watermark_cfg, ZEND_STRS("color"), (void **) &z_tmp_v) == SUCCESS) {
					convert_to_string(z_tmp_v);
					if (Z_STRLEN_P(z_tmp_v) == 7) {
						used_color = 1;
						convert_color_hex2rgb(Z_STRVAL_P(z_tmp_v), &watermark.red, &watermark.green, &watermark.blue);
					}
				}
				//r g b
				if (0 == used_color && tclip_zend_hash_find(h_watermark_cfg, ZEND_STRS("red"), (void **) &z_tmp_v) == SUCCESS) {
					convert_to_long(z_tmp_v);
					watermark.red = (int) Z_LVAL_P(z_tmp_v);
					if (watermark.red > 255) {
						watermark.red = 255;
					}
					if (watermark.red < 0) {
						watermark.red = 0;
					}
				}
				if (0 == used_color && tclip_zend_hash_find(h_watermark_cfg, ZEND_STRS("green"), (void **) &z_tmp_v) == SUCCESS) {
					convert_to_long(z_tmp_v);
					watermark.green = (int) Z_LVAL_P(z_tmp_v);
					if (watermark.green > 255) {
						watermark.green = 255;
					}
					if (watermark.green < 0) {
						watermark.green = 0;
					}
				}
				if (0 == used_color && tclip_zend_hash_find(h_watermark_cfg, ZEND_STRS("blue"), (void **) &z_tmp_v) == SUCCESS) {
					convert_to_long(z_tmp_v);
					watermark.blue = (int) Z_LVAL_P(z_tmp_v);
					if (watermark.blue > 255) {
						watermark.blue = 255;
					}
					if (watermark.blue < 0) {
						watermark.blue = 0;
					}
				}
				//thickness
				if (tclip_zend_hash_find(h_watermark_cfg, ZEND_STRS("thickness"), (void **) &z_tmp_v) == SUCCESS) {
					convert_to_long(z_tmp_v);
					watermark.thickness = (int) Z_LVAL_P(z_tmp_v);
				}
				break;
			case IS_STRING:
				watermark.text = Z_STRVAL_P(z_watermark_cfg);
				watermark.text_len = Z_STRLEN_P(z_watermark_cfg);
				break;
		}
	}

	image = imread(sourcePath);
    if (!image.data){
        php_error_docref(NULL TSRMLS_CC, E_WARNING, "fail to load image from %s", sourcePath);
        RETURN_FALSE;
    }
	//宽度的3倍小于高度
    if( image.size().width * 3 <= image.size().height ){
        ratio = (float) destWidth/image.size().width;
        tmpSize = Size((int)(image.size().width* ratio), (int)(image.size().height * ratio));
        destImage = Mat(tmpSize, CV_32S);
        resize(image, destImage, tmpSize);
        clipTop = 0;
        clipBottom = destHeight - destImage.size().height;
        clipLeft = 0;
        clipRight = 0;
        destImage.adjustROI(clipTop, clipBottom, clipLeft, clipRight);
        imwrite(destPath, destImage);
        RETURN_TRUE;
    }

	//
    ratio = (float)300.0 / image.size().width;
    tmpSize = Size((int)(image.size().width* ratio), (int)(image.size().height * ratio));
    destImage = Mat(tmpSize, CV_32S);
    resize(image, destImage, tmpSize);
    result = detectFace( destImage TSRMLS_CC);
    if(result == -1){
        result = detectCharacter( destImage TSRMLS_CC);
    }
    result = result == -1 ? -1 : (int)((float)result / ratio);
    ratioWidth = (float)destWidth / image.size().width;
	ratioHeight = (float)destHeight / image.size().height;
    if (ratioWidth > ratioHeight) {
		ratio = ratioWidth;
	} else {
		ratio = ratioHeight;
	}
    result = result == -1 ? -1 : (int)((float)result * ratio);
    tmpSize = Size((int)(image.size().width* ratio), (int)(image.size().height * ratio));
    destImage = Mat(tmpSize, CV_32S);
    resize(image, destImage, tmpSize);

    //原图片 宽度小于高度
    if (ratioWidth > ratioHeight) {
		if (result == -1) {
			clipTop = -((destImage.size().height - destHeight) / 2);
			clipBottom = clipTop;
		} else {
			if (destImage.size().height - result >= destHeight) {
				clipTop = -result;
				clipBottom = -(destImage.size().height - result - destHeight);
			} else {
				clipTop = -(destImage.size().height - destHeight);
			}
		}
	} else {
		clipLeft = -((destImage.size().width - destWidth) / 2);
		clipRight = clipLeft;
	}
    //Mat& Mat::adjustROI(int dtop, int dbottom, int dleft, int dright)
    destImage.adjustROI(clipTop, clipBottom, clipLeft, clipRight); 
    string waterMarkTest = watermark.text;

    if (watermark.text_len > 0) {
		if (watermark.x == 0 && watermark.y == 0) {
			watermark.x = 10;
			watermark.y = destImage.rows - 20;
		}
	    putText(destImage, waterMarkTest , Point(watermark.x, watermark.y), watermark.font, watermark.scale, CV_RGB(watermark.red,watermark.green,watermark.blue), watermark.thickness);
	}
    try {
		imwrite(destPath, destImage);
	} catch (exception &e) {
		php_error_docref(NULL TSRMLS_CC, E_WARNING, "%s", e.what());
        RETURN_FALSE;
	}
	RETURN_TRUE;
}
/* }}} */


#ifdef COMPILE_DL_TCLIP
#ifdef ZTS
ZEND_TSRMLS_CACHE_DEFINE()
#endif
ZEND_GET_MODULE(tclip)
#endif

/*
 * Local variables:
 * tab-width: 4
 * c-basic-offset: 4
 * End:
 * vim600: noet sw=4 ts=4 fdm=marker
 * vim<600: noet sw=4 ts=4
 */
